#include <stdio.h>
/*
  Codec2 demo

  Copyright (C) 2011 Peter Lawrence

  All rights reserved.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License version 2.1, as
  published by the Free Software Foundation.  This program is
  distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with this program; if not, see <http://www.gnu.org/licenses/>.
*/

#include <windows.h>
#include "resource.h"
#include "codec2.h"

/* general function prototypes */
BOOL CALLBACK DlgProc (HWND, UINT, WPARAM, LPARAM);

/* local function prototypes */
static void fill_wavein_combo_box(HWND hwnd);
static void fill_waveout_combo_box(HWND hwnd);
static void resize_combo_box(HWND hwnd, int identifier, int count);

/* local constants */
static const TCHAR szAppName[]   = TEXT ("Codec2 Demo");
static const TCHAR szOpenError[] = TEXT ("Error opening soundcard!");
static const TCHAR szMemError[]  = TEXT ("Error allocating memory!");
static const TCHAR szC2Error[]   = TEXT ("Codec2 initialization failed!");

/* local variables */
static void *codec2_2500, *codec2_1400;
static unsigned char *c2_frame;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
	if (-1 == DialogBox (hInstance, TEXT ("Record"), NULL, DlgProc))
	{
		MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR);
	}

	return 0;
}

BOOL CALLBACK DlgProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static BOOL bActive, bTerminating;
	static HWAVEIN hWaveIn;
	static HWAVEOUT hWaveOut;
	static PBYTE pBuffer1, pBuffer2;
	static PWAVEHDR pWaveHdr1, pWaveHdr2, pWaveOutHdr, pWaveParmHdr;
	static WAVEFORMATEX waveform;
	static UINT wavein_deviceid, waveout_deviceid;

	static int iteration;
	short *c2in, *c2out;
	int i;
	static int audio_buffer_size;
	void *codec2_context;

	switch (message)
	{
	case WM_INITDIALOG:

		/* allocate memory for wavIn headers */
		pWaveHdr1 = malloc (sizeof (WAVEHDR));
		pWaveHdr2 = malloc (sizeof (WAVEHDR));

		/* initialize instances of Codec2 (one for each bitrate) */
		codec2_2500 = codec2_create(CODEC2_MODE_2500);
		codec2_1400 = codec2_create(CODEC2_MODE_1400);

		/* determine worst case Codec2 frame size */
		i = codec2_bits_per_frame(codec2_2500);
		if (codec2_bits_per_frame(codec2_1400) > i)
			i = codec2_bits_per_frame(codec2_1400);

		/* allocate memory for Codec2 frame size */
		c2_frame = malloc((i + 7) / 8);

		/* determine worst case Codec2 samples per frame */
		audio_buffer_size = codec2_samples_per_frame(codec2_2500);
		if (codec2_samples_per_frame(codec2_1400) > audio_buffer_size)
			audio_buffer_size = codec2_samples_per_frame(codec2_1400);
		
		/* scale value from units of samples to bytes */
		audio_buffer_size *= sizeof(short);

		/* initialize audio input choices, picking default */
		fill_wavein_combo_box(hwnd);
		SendMessage(GetDlgItem(hwnd, IDC_CHOOSE_IN), CB_SETCURSEL, 0, 0);

		/* initialize audio output choices, picking default */
		fill_waveout_combo_box(hwnd);
		SendMessage(GetDlgItem(hwnd, IDC_CHOOSE_OUT), CB_SETCURSEL, 0, 0);

		/* initialize codec choice to unencoded */
		SendMessage(GetDlgItem(hwnd, IDC_RAW), BM_SETCHECK, 1, 0);
		SendMessage(GetDlgItem(hwnd, IDC_2500), BM_SETCHECK, 0, 0);
		SendMessage(GetDlgItem(hwnd, IDC_1400), BM_SETCHECK, 0, 0);

		/* codec choice is disabled before Start button is pressed (so user doesn't try to select a codec and expect to hear something) */
		EnableWindow (GetDlgItem (hwnd, IDC_RAW), FALSE);
		EnableWindow (GetDlgItem (hwnd, IDC_1400), FALSE);
		EnableWindow (GetDlgItem (hwnd, IDC_2500), FALSE);

		/* initialize for later use */
		waveform.wFormatTag      = WAVE_FORMAT_PCM;
		waveform.nChannels       = 1;
		waveform.nSamplesPerSec  = 8000;
		waveform.nAvgBytesPerSec = 16000;
		waveform.nBlockAlign     = 1;
		waveform.wBitsPerSample  = 16;
		waveform.cbSize          = 0;

		return TRUE;

	case WM_COMMAND:
		switch (LOWORD (wParam))
		{
		case IDC_START:
			/* allocate buffer memory for input audio; software will ping-pong between these two buffers */
			pBuffer1 = malloc (audio_buffer_size);
			pBuffer2 = malloc (audio_buffer_size);

			/* alert user if there is a problem */
			if (!pBuffer1 || !pBuffer2 || !c2_frame)
			{
				if (pBuffer1) free (pBuffer1);
				if (pBuffer2) free (pBuffer2);

				MessageBeep (MB_ICONEXCLAMATION);
				MessageBox (hwnd, szMemError, szAppName, MB_ICONEXCLAMATION | MB_OK);
				return TRUE;
			}

			/* alert user if there was a problem with Codec2 */
			if (!codec2_2500)
			{
				MessageBeep (MB_ICONEXCLAMATION);
				MessageBox (hwnd, szC2Error, szAppName, MB_ICONEXCLAMATION | MB_OK);
				return TRUE;
			}

			/* open input audio and alert user if there is a problem */

			if (waveInOpen (&hWaveIn, (wavein_deviceid) ? (wavein_deviceid - 1) : WAVE_MAPPER, &waveform, (DWORD) hwnd, 0, CALLBACK_WINDOW))
			{
				MessageBeep (MB_ICONEXCLAMATION);
				MessageBox (hwnd, szOpenError, szAppName, MB_ICONEXCLAMATION | MB_OK);
			}

			/* prepare waveform headers (one for each of the two buffers) */

			pWaveHdr1->lpData          = pBuffer1;
			pWaveHdr1->dwBufferLength  = audio_buffer_size;
			pWaveHdr1->dwBytesRecorded = 0;
			pWaveHdr1->dwUser          = 0;
			pWaveHdr1->dwFlags         = 0;
			pWaveHdr1->dwLoops         = 1;
			pWaveHdr1->lpNext          = NULL;
			pWaveHdr1->reserved        = 0;

			waveInPrepareHeader (hWaveIn, pWaveHdr1, sizeof (WAVEHDR));

			pWaveHdr2->lpData          = pBuffer2;
			pWaveHdr2->dwBufferLength  = audio_buffer_size;
			pWaveHdr2->dwBytesRecorded = 0;
			pWaveHdr2->dwUser          = 0;
			pWaveHdr2->dwFlags         = 0;
			pWaveHdr2->dwLoops         = 1;
			pWaveHdr2->lpNext          = NULL;
			pWaveHdr2->reserved        = 0;

			waveInPrepareHeader (hWaveIn, pWaveHdr2, sizeof (WAVEHDR));

			/* open output audio and alert user if there is a problem */

			if (waveOutOpen (&hWaveOut, (waveout_deviceid) ? (waveout_deviceid - 1) : WAVE_MAPPER, &waveform, (DWORD) hwnd, 0, CALLBACK_WINDOW))
			{
				MessageBeep (MB_ICONEXCLAMATION);
				MessageBox (hwnd, szOpenError, szAppName, MB_ICONEXCLAMATION | MB_OK);
			}

			return TRUE;

		case IDC_STOP:

			/* update flag so that code can act accordingly on the next audio event */
			bActive = FALSE;

			return TRUE;

		case IDC_CHOOSE_IN:

			/* handle events from audio input selection control */
			switch (HIWORD(wParam))
			{
			case CBN_DROPDOWN:
				fill_wavein_combo_box(hwnd);
				break;
			case CBN_CLOSEUP:
				wavein_deviceid = SendMessage(GetDlgItem(hwnd, IDC_CHOOSE_IN), CB_GETCURSEL, 0, 0);
				if (-1 == wavein_deviceid)
				{
					SendMessage(GetDlgItem(hwnd, IDC_CHOOSE_IN), CB_SETCURSEL, 0, 0);
					wavein_deviceid = 0;
				}
				break;
			}

			return TRUE;

		case IDC_CHOOSE_OUT:

			/* handle events from audio output selection control */
			switch (HIWORD(wParam))
			{
			case CBN_DROPDOWN:
				fill_waveout_combo_box(hwnd);
				break;
			case CBN_CLOSEUP:
				waveout_deviceid = SendMessage(GetDlgItem(hwnd, IDC_CHOOSE_OUT), CB_GETCURSEL, 0, 0);
				if (-1 == waveout_deviceid)
				{
					SendMessage(GetDlgItem(hwnd, IDC_CHOOSE_OUT), CB_SETCURSEL, 0, 0);
					waveout_deviceid = 0;
				}
				break;
			}
			return TRUE;
		}

		case IDC_RAW:
		case IDC_2500:
		case IDC_1400:

			/* update codec selection radio buttons */
			SendMessage(GetDlgItem(hwnd, IDC_RAW), BM_SETCHECK, (IDC_RAW == LOWORD (wParam)) ? 1 : 0, 0);
			SendMessage(GetDlgItem(hwnd, IDC_2500), BM_SETCHECK, (IDC_2500 == LOWORD (wParam)) ? 1 : 0, 0);
			SendMessage(GetDlgItem(hwnd, IDC_1400), BM_SETCHECK, (IDC_1400 == LOWORD (wParam)) ? 1 : 0, 0);

			return TRUE;

		break;

	case MM_WIM_OPEN:

		/* audio input is now active; grey-out appropriate controls */

		EnableWindow (GetDlgItem (hwnd, IDC_START), FALSE);
		EnableWindow (GetDlgItem (hwnd, IDC_STOP), TRUE) ;
		EnableWindow (GetDlgItem (hwnd, IDC_RAW), TRUE);
		EnableWindow (GetDlgItem (hwnd, IDC_1400), (codec2_1400) ? TRUE : FALSE);
		EnableWindow (GetDlgItem (hwnd, IDC_2500), TRUE);
		EnableWindow (GetDlgItem (hwnd, IDC_CHOOSE_IN), FALSE);
		EnableWindow (GetDlgItem (hwnd, IDC_CHOOSE_OUT), FALSE);
		SetFocus (GetDlgItem (hwnd, IDC_STOP));

		/* add the buffers */

		waveInAddBuffer (hWaveIn, pWaveHdr1, sizeof (WAVEHDR));
		waveInAddBuffer (hWaveIn, pWaveHdr2, sizeof (WAVEHDR));

		/* update flag */
		bActive = TRUE;

		/* start sampling */
		waveInStart (hWaveIn);

		return TRUE;

	case MM_WIM_DATA:

		/* input audio has arrived; set pointer to WAVEHDR that describes the audio */
		pWaveParmHdr = ((PWAVEHDR) lParam);

		/* allocate memory to store the Codec2 encoded/decoded result */
		pWaveOutHdr = malloc (sizeof (WAVEHDR));
		pWaveOutHdr->lpData = malloc(pWaveParmHdr->dwBytesRecorded);

		if (pWaveOutHdr->lpData && pWaveOutHdr)
		{
			/* set pointers to audio in and out */
			c2in = (short *)pWaveParmHdr->lpData;
			c2out = (short *)pWaveOutHdr->lpData;

			/* branch according to the codec selection radio buttons... */

			if (SendMessage(GetDlgItem(hwnd, IDC_RAW), BM_GETCHECK, 0, 0))
			{
				/* uncompressed pass through */
				memcpy(c2out, c2in, pWaveParmHdr->dwBytesRecorded);
			}
			else
			{
				/* Codec2 is selected; pick between the two bit rates */
				if (SendMessage(GetDlgItem(hwnd, IDC_2500), BM_GETCHECK, 0, 0))
					codec2_context = codec2_2500;
				else
					codec2_context = codec2_1400;

				/* encode and then decode all the audio frames */
				for (i = 0; i < (int)(pWaveParmHdr->dwBytesRecorded / sizeof(short)); i += codec2_samples_per_frame(codec2_context))
				{
					codec2_encode(codec2_context, c2_frame, c2in + i);
					codec2_decode(codec2_context, c2out + i, c2_frame);
				}
			}

			/* prepare the header for the waveform output */
			pWaveOutHdr->dwBufferLength  = pWaveParmHdr->dwBytesRecorded;
			pWaveOutHdr->dwBytesRecorded = 0;
			pWaveOutHdr->dwUser          = 0;
			pWaveOutHdr->dwFlags         = 0;
			pWaveOutHdr->dwLoops         = 1;
			pWaveOutHdr->lpNext          = NULL;
			pWaveOutHdr->reserved        = 0;

			/* set the begin and end flags appropriately */
			if (0 == iteration++)
				pWaveOutHdr->dwFlags |= WHDR_BEGINLOOP;
			if (!bActive)
				pWaveOutHdr->dwFlags |= WHDR_ENDLOOP;

			/* send the output audio for playback */
			waveOutPrepareHeader (hWaveOut, pWaveOutHdr, sizeof (WAVEHDR));
			waveOutWrite (hWaveOut, pWaveOutHdr, sizeof (WAVEHDR));
		}

		/* if we are no longer active, close the input audio */
		if (!bActive)
		{
			waveInClose (hWaveIn);
			iteration = 0;
			return TRUE;
		}

		/* if we've reached here, we must be still active, so recycle the buffer by offering it back to the soundcard */
		waveInAddBuffer (hWaveIn, pWaveParmHdr, sizeof (WAVEHDR));

		return TRUE;

	case MM_WIM_CLOSE:
		
		/* audio input has closed, so free the buffer memory */
		waveInUnprepareHeader (hWaveIn, pWaveHdr1, sizeof (WAVEHDR));
		waveInUnprepareHeader (hWaveIn, pWaveHdr2, sizeof (WAVEHDR));
		free (pBuffer1);
		free (pBuffer2);

		/* grey-out appropriate controls */
		EnableWindow (GetDlgItem (hwnd, IDC_START), TRUE);
		EnableWindow (GetDlgItem (hwnd, IDC_STOP), FALSE);
		EnableWindow (GetDlgItem (hwnd, IDC_RAW), FALSE);
		EnableWindow (GetDlgItem (hwnd, IDC_1400), FALSE);
		EnableWindow (GetDlgItem (hwnd, IDC_2500), FALSE);
		EnableWindow (GetDlgItem (hwnd, IDC_CHOOSE_IN), TRUE);
		EnableWindow (GetDlgItem (hwnd, IDC_CHOOSE_OUT), TRUE);
		SetFocus (GetDlgItem (hwnd, IDC_START));

		return TRUE;

	case MM_WOM_OPEN:

		/* audio output has been opened */

		return TRUE;

	case MM_WOM_DONE:

		/* soundcard is done with the output buffer provided, so we free the memory */
		pWaveParmHdr = ((PWAVEHDR) lParam);
		waveOutUnprepareHeader (hWaveOut, pWaveParmHdr, sizeof (WAVEHDR));
		free(pWaveParmHdr->lpData);

		/* if we are no longer active, close the output audio */
		if (!bActive)
		{
			waveOutClose (hWaveOut);
		}

		return TRUE;

	case MM_WOM_CLOSE:

		/* audio output has been closed */
		
		/* if we are terminating, we send ourself a message to close up */
		if (bTerminating)
			SendMessage (hwnd, WM_SYSCOMMAND, SC_CLOSE, 0L);

		return TRUE;

	case WM_SYSCOMMAND:

		switch (LOWORD (wParam))
		{
		case SC_CLOSE:

			/* if we are still active, we update the flags and allow the software to close shop gracefully */
			if (bActive)
			{
				bTerminating = TRUE;
				bActive = FALSE;
				return TRUE;
			}

			/* we must not be active, so we can free memory and end the program */
			free (pWaveHdr1);
			free (pWaveHdr2);
			free (c2_frame);
			codec2_destroy(codec2_2500);
			if (codec2_1400)
				codec2_destroy(codec2_1400);

			EndDialog (hwnd, 0);

			return TRUE;
		}
		break;
	}
	return FALSE;
}

/*
an oddity of Windows is that apparently there is no nice way to resize a drop-down combo box to reflect the number of entries
this is the only way (however kludgy) that I know of to do this
*/

static void resize_combo_box(HWND hwnd, int identifier, int count)
{
	RECT LboxRc;
	int Height;
	HWND handle;

	handle = GetDlgItem(hwnd, identifier);

	if (handle)
	{
		GetWindowRect(handle, &LboxRc);

		Height = SendMessage(handle, CB_GETITEMHEIGHT, 0, 0) * count;
		Height += SendMessage(handle, CB_GETITEMHEIGHT, -1, 0);
		Height += GetSystemMetrics(SM_CYEDGE) * 2;
		Height += GetSystemMetrics(SM_CYEDGE) * 2;

		SetWindowPos(handle, NULL, 0, 0, LboxRc.right - LboxRc.left, Height, SWP_NOMOVE | SWP_NOZORDER );
	}
}

/* fill in all the audio input choices */

static void fill_wavein_combo_box(HWND hwnd)
{
	WAVEINCAPS wic;
	int iNumDevs, i;

	iNumDevs = waveInGetNumDevs();

	SendMessage(GetDlgItem(hwnd, IDC_CHOOSE_IN), CB_RESETCONTENT, 0, 0);

	SendMessage(GetDlgItem(hwnd, IDC_CHOOSE_IN), CB_ADDSTRING, 0, (LPARAM)"default input");

	for (i = 0; i < iNumDevs; i++)
	{
		if (!waveInGetDevCaps(i, &wic, sizeof(WAVEINCAPS)))
		{
			SendMessage(GetDlgItem(hwnd, IDC_CHOOSE_IN), CB_ADDSTRING, 0, (LPARAM)wic.szPname);
		}
	}

	resize_combo_box(hwnd, IDC_CHOOSE_IN, iNumDevs + 1);
}

/* fill in all the audio output choices */

static void fill_waveout_combo_box(HWND hwnd)
{
	WAVEOUTCAPS wic;
	int iNumDevs, i;

	iNumDevs = waveOutGetNumDevs();

	SendMessage(GetDlgItem(hwnd, IDC_CHOOSE_OUT), CB_RESETCONTENT, 0, 0);

	SendMessage(GetDlgItem(hwnd, IDC_CHOOSE_OUT), CB_ADDSTRING, 0, (LPARAM)"default output");

	for (i = 0; i < iNumDevs; i++)
	{
		if (!waveOutGetDevCaps(i, &wic, sizeof(WAVEOUTCAPS)))
		{
			SendMessage(GetDlgItem(hwnd, IDC_CHOOSE_OUT), CB_ADDSTRING, 0, (LPARAM)wic.szPname);
		}
	}

	resize_combo_box(hwnd, IDC_CHOOSE_OUT, iNumDevs + 1);
}
